GRAY_CNN(图片灰度化处理+图片识别)
应用概述
这里将以图片灰度化加图片识别来介绍AI+DSP应用开发的开发流程。 其中图片灰度化可以使用DSP来完成,图片识别则使用AI来完成。 灰度化处理使用灰度化公式来进行,图片识别使用CNN模型。
开发流程
1. 定义模型
图片灰度化处理过程。
这里实现图片灰度化,使用蓝、绿、红三个通道的值进行加权求和,计算出一个灰度值。 这里使用的权重分别是0.114、0.587和0.299,这些数值是基于人眼对不同颜色的敏感度来选择的, 用于将彩色图像转换为灰度图像。公式为:
\[Gray = B \times 0.114 + G \times 0.587 + R \times 0.299\]以及定义一个Clip操作,确保灰度值在0到255之间。 代码示例如下:
1import mindspore as ms 2from mindspore.train.serialization import export 3import numpy as np 4from mindspore import nn, ops 5 6class Gray(nn.Cell): 7 def __init__(self): 8 super(Gray, self).__init__() 9 self.split = ops.Split(axis=2, output_num=3) 10 def construct(self, x): 11 x = self.split(x) 12 b, g, r = x 13 x = ( 14 b * 0.114 + 15 g * 0.587 + 16 r * 0.299 17 ).squeeze(-1) 18 x = ops.clip_by_value(x, 0, 255) 19 return x
定义AI模型,用于图片识别。
这里的AI模型是卷积神经网络(CNN)。CNN主要由卷积,池化,激活等算子组成; CNN模型架构如下表所示:
层级
操作类型
参数细节
输出维度
输入层
灰度图像输入
1通道,尺寸 H×W
H×W×1
卷积块1
Conv2d
输入通道:1 → 输出通道:32, 卷积核:3×3
H×W×32
ReLU激活
非线性变换
H×W×32
MaxPool2d
窗口:2×2, 步长:2
H/2×W/2×32
卷积块2
Conv2d
输入通道:32 → 输出通道:64, 卷积核:3×3
H/2×W/2×64
ReLU激活
非线性变换
H/2×W/2×64
MaxPool2d
窗口:2×2, 步长:2
H/4×W/4×64
卷积块3
Conv2d
输入通道:64 → 输出通道:128, 卷积核:3×3
H/4×W/4×128
ReLU激活
非线性变换
H/4×W/4×128
MaxPool2d
窗口:2×2, 步长:2
H/8×W/8×128
全连接层
Flatten
展平多维特征图
32768 (H/8×W/8×128)
Dense
输入:32768 → 输出:64, 激活:ReLU
64
Dense (输出层)
输入:64 → 输出:5, 无激活
5
输出层
分类结果
5类概率分布
5
CNN模型用于图片识别,不能直接导出模型,需要训练模型,该部分内容见: 2. 模型训练
定义CNN模型的代码示例如下:
1import mindspore as ms 2from mindspore import nn 3class CNN(nn.Cell): 4def __init__(self): 5 super().__init__() 6 self.conv1 = nn.Conv2d(1, 32, kernel_size=3, has_bias=True) 7 self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2) 8 self.conv2 = nn.Conv2d(32, 64, kernel_size=3, has_bias=True) 9 self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2) 10 self.conv3 = nn.Conv2d(64, 128, kernel_size=3, has_bias=True) 11 self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2) 12 self.flatten = nn.Flatten() 13 self.dense1 = nn.Dense(32768, 64) 14 self.dense2 = nn.Dense(64, 5) 15 self.relu = nn.ReLU() 16 17def construct(self, x): 18 x = self.relu(self.conv1(x)) 19 x = self.pool1(x) 20 x = self.relu(self.conv2(x)) 21 x = self.pool2(x) 22 x = self.relu(self.conv3(x)) 23 x = self.pool3(x) 24 x = self.flatten(x) 25 x = self.relu(self.dense1(x)) 26 x = self.dense2(x) 27 return x
2. 模型训练
CNN模型需要进行训练才能正确识别,训练需要数据集,这里已经提前准备好数据集,放在Target文件夹下, 使用MindSpore框架进行模型训练,需要导入相关库和模块,定义数据预处理、模型结构、损失函数和优化器等。 重新组网时,直接使用
nn.GraphCell()接口会导致权重丢失, 可以在训练前时使用ms.save_checkpoint()接口保存成ckpt文件, 重新组网时,使用ms.load_checkpoint()接口加载ckpt文件即可。 以下代码展示了如何加载数据集,进行10次模型训练,以及导出模型。 训练以及导出模型代码如下:1import mindspore as ms 2from mindspore.train.serialization import export 3from mindspore import nn, context 4import numpy as np 5from PIL import Image 6import mindspore.dataset as ds 7from mindspore.dataset import py_transforms 8import mindspore.dataset.vision as CV 9from mindspore.train.callback import LossMonitor 10 11batch_size = 32 12img_size = (128, 128) 13data_path = './Target' 14 15# 数据预处理 16def rescale_to_0_1(image): 17 return image / 255.0 18 19# 自定义函数,添加 color 通道维度 20def add_channels(image): 21 if len(image.shape) == 2: # 单个图像,没有 cin_channel 维度 22 image_four_channels = np.expand_dims(image, axis=0) 23 else: 24 pass 25 return image 26 return image_four_channels 27 28def export_cnn(): 29 context.set_context(mode=context.PYNATIVE_MODE, device_target="CPU") 30 resize_op = CV.Resize(img_size) 31 rescale_transform = ms.dataset.transforms.Compose([rescale_to_0_1]) 32 f32_typecast = ms.dataset.transforms.TypeCast(ms.float32) 33 # 将读取的 RGB 转为 GRAY 模式 34 convert_gray = ms.dataset.vision.ConvertColor(ms.dataset.vision.ConvertMode.COLOR_RGB2GRAY) 35 36 transform = [convert_gray, resize_op, f32_typecast, rescale_transform, CV.HWC2CHW()] 37 train_data = ds.ImageFolderDataset(dataset_dir=data_path, decode=True, extensions=[".JPEG", ".PNG", ".JPG"]) 38 train_data = train_data.map(input_columns="image", operations= transform) 39 train_data = train_data.map(operations=lambda image: add_channels(image), \ 40 input_columns=["image"], \ 41 output_columns=["image"]) 42 train_data = train_data.batch(batch_size=batch_size) 43 data_iter = train_data.create_dict_iterator() 44 print("数据集加载完成") 45 46 my_model = CNN() 47 loss = nn.CrossEntropyLoss(reduction='mean') 48 optimizer = nn.Adam(my_model.trainable_params(), learning_rate=0.01) 49 cnn_model = ms.Model(my_model, loss_fn=loss, optimizer=optimizer, metrics={'Accuracy': nn.Accuracy()}) 50 print("网络构建完成") 51 num_epoch = 10 52 cnn_model.train(num_epoch, train_data,callbacks=[LossMonitor()],dataset_sink_mode=False) 53 inputs = ms.Tensor(np.random.randn(1, 1, 128, 128).astype(np.float32)) 54 ms.save_checkpoint(my_model, 'cnn.ckpt') 55 export(my_model, inputs, file_name='cnn', file_format="MINDIR")
3. 重新组网
在前面章节中,我们已经完成了AI的模型的训练和导出。 需要重新构建成一个新的网络结构,可以使用MindSpore框架的
nn.GraphCell()接口来实现。 该部分内容包括加载训练好的cnn模型,灰度化处理,重新构建网络结构,并导出新的模型。 重新组网、导出模型以及测试的代码如下:1import cv2 2import mindspore as ms 3from mindspore.train.serialization import export 4import numpy as np 5from mindspore import nn, ops, context 6from CNN import export_cnn 7from GRAY import export_gray 8from Connection import export_connection 9 10class_names = ['BRDM_2', 'BTR_60', 'SLICY', 'T62', 'ZSU_23_4'] 11class_labels = ["装甲侦察车", "装甲运输车", "不明", "坦克", "自行高炮"] 12class GrayCNN(nn.Cell): 13 def __init__(self): 14 super(GrayCNN, self).__init__() 15 self.split = ops.Split(axis=2, output_num=3) 16 self.conv1 = nn.Conv2d(1, 32, kernel_size=3, has_bias=True) 17 self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2) 18 self.conv2 = nn.Conv2d(32, 64, kernel_size=3, has_bias=True) 19 self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2) 20 self.conv3 = nn.Conv2d(64, 128, kernel_size=3, has_bias=True) 21 self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2) 22 self.flatten = nn.Flatten() 23 self.dense1 = nn.Dense(32768, 64) 24 self.dense2 = nn.Dense(64, 5) 25 self.relu = nn.ReLU() 26 self.cnn = nn.GraphCell(ms.load(file_name="cnn.mindir")) 27 def construct(self, x): 28 x = self.split(x) 29 b, g, r = x 30 x = ( 31 b * 0.114 + 32 g * 0.587 + 33 r * 0.299 34 ).squeeze(-1) 35 x = ops.clip_by_value(x, 0, 255) 36 x = x / 255.0 # 数据归一化 37 x = x.reshape(1, 1, 128, 128) # 灰度化处理后结果需要重塑成cnn输入形状 38 x = self.cnn(x) 39 return x 40 41if __name__ == "__main__": 42 export_cnn() 43 context.set_context(mode=context.GRAPH_MODE) 44 # 1. 读取图片(保持原始uint8类型) 45 color_image = cv2.imread("image_origin.jpg") # 默认uint8 46 resized_image = cv2.resize( 47 color_image, 48 (128, 128), # 目标尺寸(width, height) 49 interpolation=cv2.INTER_LINEAR 50 ) 51 resized_image = resized_image.astype(np.float32) 52 resized_image_tensor = ms.Tensor(resized_image) 53 54 my_model = GrayCNN() 55 param_dict = ms.load_checkpoint("cnn.ckpt") 56 param_not_load, _ = ms.load_param_into_net(my_model, param_dict, strict_load=True) 57 58 input_tensor = ms.Tensor(np.ones((128, 128, 3), dtype=np.float32)) 59 export(my_model, input_tensor, file_name='gray_cnn', file_format='MINDIR') 60 61 reload_cnn = nn.GraphCell(ms.load(file_name='gray_cnn.mindir')) 62 reload_cnn = ms.Model(reload_cnn) 63 m = reload_cnn.predict(resized_image_tensor) 64 print(m) 65 output_np = m.asnumpy() 66 if len(output_np.shape) == 2: # 对于分类任务,通常输出是[batch_size, num_classes] 67 output_prob = np.exp(output_np) / np.exp(output_np).sum(axis=1, keepdims=True) 68 predicted_class = np.argmax(output_prob, axis=1) 69 print(f"预测结果: {class_labels[predicted_class[0]]}")该代码首先定义了一个新的网络结构GrayCNN, 在
construct方法中,首先通过gray模型处理输入图像,然后通过归一化和重塑张量,最后cnn模型进行识别。 在主函数中,首先导出cnn模型。 然后加载cnn模型参数,并使用MindSpore的export方法导出新的网络结构。 最后,使用重新组网后的模型进行预测,并输出结果。
4. 转换模型
在前面的代码中,我们已经使用了
export方法导出了gray_cnn.mindir模型。 要将该模型部署到FT78NE平台,需要将MINDIR格式转换成mindspore lite的ms格式模型。 要将该模型转换为ms格式,可以使用MindSpore的转换工具(converter_lite)。 该工具已经集成在我们的YHFT-IDE中,可以直接在IDE中使用。或者也可以使用命令行工具进行转换。转换命令如下:
./converter_lite --fmk=MINDIR --modelFile=gray_cnn.mindir --outputFile=gray_cnn转换完成后,会在当前目录下生成gray_cnn.ms模型文件。该模型可以使用可视化工具(netron)可以打开该文件,查看模型结构。 该工具可以直接在
YHFT-IDE中使用,可以从官网下载使用,也可以在线使用。在线地址为:https://netron.app/。该模型文件可视化如图所示:
![]()
5. 部署和运行程序
在IDE中新建一个Python项目,将下面的 python完整代码示例 代码拷贝到项目中,并运行。 运行成功后,会在当前目录下生成一个名为
gray_cnn.midir的模型文件,以及输出图片的预测结果。 结果如图所示:![]()
将该文件转换为ms格式,并将ms格式模型和测试图片拷贝到FT78NE平台中。
打开
YHFT-IDE,新建工程。输入工程名、路径,工程类型选择Heterogeneous, 输入交叉编译工具路径,然后点确定。会生成一个异构模板工程。通过修改data_handler.cc文件中的函数来调整输入输出数据, 输入改成读取的图片路径;输出改成相应的后处理。在main函数设置运行后端;修改CMakeLists.txt文件, 添加openCV库的lib和include路径, 最后编译该工程,编译成功后将build文件夹下的main拷贝到FT78NE平台中, 要和gray_cnn.ms模型同一个文件夹下。输入的c++代码示例如下:
1#include <opencv2/opencv.hpp> 2int readImage(float *reslut, std::string imagePath) { 3 cv::Mat color_image = cv::imread(imagePath.c_str(), cv::IMREAD_COLOR); 4 if (color_image.empty()) { 5 fprintf(stderr, "read image failed\n"); 6 return -1; 7 } 8 9 int width = 128; 10 int height = 128; 11 cv::Mat resized_image; 12 cv::resize(color_image, resized_image, cv::Size(width, height), 0, 0, cv::INTER_LINEAR); 13 resized_image.convertTo(resized_image, CV_32F); 14 const int channels = resized_image.channels(); 15 const int element_count = width * height * channels; 16 17 for (int i = 0; i < height; ++i) { 18 const float *src_ptr = resized_image.ptr<float>(i); 19 float *dst_ptr = reslut + i * width * channels; 20 std::memcpy(dst_ptr, src_ptr, width * channels * sizeof(float)); 21 } 22 return 0; 23}设置后端代码如下:
1auto context = std::make_shared<mindspore::Context>(); 2auto &device_list = context->MutableDeviceInfo(); 3context->SetBuiltInDelegate(mindspore::DelegateMode::kPNNA); 4auto cpu_info = std::make_shared<mindspore::CPUDeviceInfo>(); 5auto dsp_info = std::make_shared<mindspore::FT78NEDeviceInfo>(); 6device_list.push_back(dsp_info); 7device_list.push_back(cpu_info);输出结果后处理代码如下:
1std::vector<float> floatArrayToVector(const float *array, size_t size) { 2 std::vector<float> vec(array, array + size); 3 return vec; 4} 5 6std::vector<float> numpy_exp(const std::vector<float> &x) { 7 std::vector<float> result; 8 result.reserve(x.size()); 9 for (float num : x) { 10 result.push_back(exp(num)); 11 } 12 return result; 13} 14 15float sum_rows(const std::vector<float> &vec, int axis) { 16 float sum = std::accumulate(vec.begin(), vec.end(), 0.0); 17 return sum; 18} 19 20std::vector<std::vector<float>> convertTo2D(const std::vector<float> &vec, int rows) { 21 std::vector<std::vector<float>> result; 22 if (vec.empty() || rows <= 0) { 23 return result; 24 } 25 int cols = (vec.size() + rows - 1) / rows; // 计算列数 26 for (int i = 0; i < rows; ++i) { 27 std::vector<float> row; 28 for (int j = 0; j < cols; ++j) { 29 size_t index = i * cols + j; // 计算索引 30 if (index < vec.size()) { 31 row.push_back(vec[index]); 32 } else { 33 row.push_back(0); // 填充剩余空间,或者你可以选择不填充 34 } 35 } 36 result.push_back(row); 37 } 38 return result; 39} 40 41void GetOutputData(std::vector<MSTensor> &outputs) { 42 for (auto tensor : outputs) { 43 std::cout << "tensor name is:" << tensor.Name() << " tensor size is:" << tensor.DataSize() 44 << " tensor elements num is:" << tensor.ElementNum() << std::endl; 45 auto out_data = reinterpret_cast<const float *>(tensor.Data().get()); 46 std::cout << "output data is:"; 47 for (int i = 0; i < tensor.ElementNum() && i <= 50; i++) { 48 std::cout << out_data[i] << " "; 49 } 50 std::cout << std::endl; 51 std::vector<float> out = floatArrayToVector(out_data, tensor.ElementNum()); 52 std::vector<float> exp_x = numpy_exp(out); 53 float sums = sum_rows(exp_x, 1); 54 std::vector<float> x1; 55 for (float num : exp_x) { 56 x1.push_back(num / sums); 57 } 58 std::vector<std::vector<float>> twoD = convertTo2D(x1, 1); 59 std::vector<int> argmax_indices = argmax(twoD); 60 std::vector<std::string> Predicted_class = {"装甲侦察车", "装甲运输车", "不明", "坦克", "自行高炮"}; 61 std::cout << "预测结果: " << Predicted_class[argmax_indices[0]] << std::endl; 62 } 63}
在FT78NE上运行可执行文件
main,观察输出结果。与预期结果对比,验证模型推理的正确性。 执行命令如下:1./main gray_cnn.ms image_origin.jpg执行结果如下图:
![]()
python完整代码示例
1# CNN.py 2import mindspore as ms 3from mindspore.train.serialization import export 4from mindspore import nn, context 5import numpy as np 6from PIL import Image 7import mindspore.dataset as ds 8from mindspore.dataset import py_transforms 9import mindspore.dataset.vision as CV 10from mindspore.train.callback import LossMonitor 11 12# # Define directories and parameters 13batch_size = 32 14img_size = (128, 128) 15data_path = '../cnn-sar/Target' 16 17# 数据预处理 18def rescale_to_0_1(image): 19 return image / 255.0 20 21# 自定义函数,添加 color 通道维度 22def add_channels(image): 23 if len(image.shape) == 2: # 单个图像,没有 cin_channel 维度 24 # print("before ===> ", image.shape) 25 # 添加 color 通道维度,(128x128) => (1, 128, 128) 26 image_four_channels = np.expand_dims(image, axis=0) 27 # print("after ===> ", image_four_channels.shape) 28 else: 29 pass 30 return image 31 return image_four_channels 32 33class CNN(ms.nn.Cell): 34 def __init__(self): 35 super().__init__() 36 self.conv1 = nn.Conv2d(1, 32, kernel_size=3, has_bias=True) 37 self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2) 38 self.conv2 = nn.Conv2d(32, 64, kernel_size=3, has_bias=True) 39 self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2) 40 self.conv3 = nn.Conv2d(64, 128, kernel_size=3, has_bias=True) 41 self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2) 42 self.flatten = nn.Flatten() 43 self.dense1 = nn.Dense(32768, 64) 44 self.dense2 = nn.Dense(64, 5) 45 self.relu = nn.ReLU() 46 47 def construct(self, x): 48 x = self.relu(self.conv1(x)) 49 x = self.pool1(x) 50 x = self.relu(self.conv2(x)) 51 x = self.pool2(x) 52 x = self.relu(self.conv3(x)) 53 x = self.pool3(x) 54 x = self.flatten(x) 55 x = self.relu(self.dense1(x)) 56 x = self.dense2(x) 57 return x 58 59def export_cnn(): 60 context.set_context(mode=context.PYNATIVE_MODE, device_target="CPU") 61 resize_op = CV.Resize(img_size) 62 rescale_transform = ms.dataset.transforms.Compose([rescale_to_0_1]) 63 f32_typecast = ms.dataset.transforms.TypeCast(ms.float32) 64 # 将读取的 RGB 转为 GRAY 模式 65 convert_gray = ms.dataset.vision.ConvertColor(ms.dataset.vision.ConvertMode.COLOR_RGB2GRAY) 66 67 transform = [convert_gray, resize_op, f32_typecast, rescale_transform, CV.HWC2CHW()] 68 train_data = ds.ImageFolderDataset(dataset_dir=data_path, decode=True, extensions=[".JPEG", ".PNG", ".JPG"]) 69 train_data = train_data.map(input_columns="image", operations= transform) 70 train_data = train_data.map(operations=lambda image: add_channels(image), \ 71 input_columns=["image"], \ 72 output_columns=["image"]) 73 train_data = train_data.batch(batch_size=batch_size) 74 data_iter = train_data.create_dict_iterator() 75 print("数据集加载完成") 76 my_model = CNN() 77 loss = nn.CrossEntropyLoss(reduction='mean') 78 optimizer = nn.Adam(my_model.trainable_params(), learning_rate=0.01) 79 cnn_model = ms.Model(my_model, loss_fn=loss, optimizer=optimizer, metrics={'Accuracy': nn.Accuracy()}) 80 print("网络构建完成") 81 num_epoch = 10 82 cnn_model.train(num_epoch, train_data,callbacks=[LossMonitor()],dataset_sink_mode=False) 83 inputs = ms.Tensor(np.random.randn(1, 1, 128, 128).astype(np.float32)) 84 ms.save_checkpoint(my_model, 'cnn.ckpt') 85 export(my_model, inputs, file_name='cnn', file_format="MINDIR")1# GRAY_CNN.py 2import cv2 3import mindspore as ms 4from mindspore.train.serialization import export 5import numpy as np 6from mindspore import nn, ops, context 7from CNN import export_cnn 8 9class_names = ['BRDM_2', 'BTR_60', 'SLICY', 'T62', 'ZSU_23_4'] 10class_labels = ["装甲侦察车", "装甲运输车", "不明", "坦克", "自行高炮"] 11 12class GrayCNN(nn.Cell): 13 def __init__(self): 14 super(GrayCNN, self).__init__() 15 self.split = ops.Split(axis=2, output_num=3) 16 self.conv1 = nn.Conv2d(1, 32, kernel_size=3, has_bias=True) 17 self.pool1 = nn.MaxPool2d(kernel_size=2, stride=2) 18 self.conv2 = nn.Conv2d(32, 64, kernel_size=3, has_bias=True) 19 self.pool2 = nn.MaxPool2d(kernel_size=2, stride=2) 20 self.conv3 = nn.Conv2d(64, 128, kernel_size=3, has_bias=True) 21 self.pool3 = nn.MaxPool2d(kernel_size=2, stride=2) 22 self.flatten = nn.Flatten() 23 self.dense1 = nn.Dense(32768, 64) 24 self.dense2 = nn.Dense(64, 5) 25 self.relu = nn.ReLU() 26 self.cnn = nn.GraphCell(ms.load(file_name="cnn.mindir")) 27 28 def construct(self, x): 29 x = self.split(x) 30 b, g, r = x 31 x = ( 32 b * 0.114 + 33 g * 0.587 + 34 r * 0.299 35 ).squeeze(-1) 36 x = ops.clip_by_value(x, 0, 255) 37 x = x / 255.0 # 数据归一化 38 x = x.reshape(1, 1, 128, 128) # 灰度化处理后结果需要重塑成cnn输入形状 39 x = self.cnn(x) 40 return x 41 42if __name__ == "__main__": 43 export_cnn() 44 context.set_context(mode=context.GRAPH_MODE) 45 # 1. 读取图片(保持原始uint8类型) 46 color_image = cv2.imread("image_origin.jpg") # 默认uint8 47 resized_image = cv2.resize( 48 color_image, 49 (128, 128), # 目标尺寸(width, height) 50 interpolation=cv2.INTER_LINEAR 51 ) 52 resized_image = resized_image.astype(np.float32) 53 resized_image_tensor = ms.Tensor(resized_image) 54 my_model = GrayCNN() 55 param_dict = ms.load_checkpoint("cnn.ckpt") 56 param_not_load, _ = ms.load_param_into_net(my_model, param_dict, strict_load=True) 57 input_tensor = ms.Tensor(np.ones((128, 128, 3), dtype=np.float32)) 58 export(my_model, input_tensor, file_name='gray_cnn', file_format='MINDIR') 59 reload_cnn = nn.GraphCell(ms.load(file_name='gray_cnn.mindir')) 60 reload_cnn = ms.Model(reload_cnn) 61 m = reload_cnn.predict(resized_image_tensor) 62 print(m) 63 output_np = m.asnumpy() 64 if len(output_np.shape) == 2: # 对于分类任务,通常输出是[batch_size, num_classes] 65 output_prob = np.exp(output_np) / np.exp(output_np).sum(axis=1, keepdims=True) 66 predicted_class = np.argmax(output_prob, axis=1) 67 print(f"预测结果: {class_labels[predicted_class[0]]}")